// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/base/order/symbol_cache.h"

#include <string>

#include "coin2/exchange/binance/symbology/currency.h"
#include "coin2/exchange/bitmex/symbology/currency.h"
#include "coin2/exchange/ftx/symbology/currency.h"
#include "coin2/exchange/prex/symbology/currency.h"

namespace coin2::exchange::base::order {

namespace {
std::string ToUpper(const std::string& input) {
  std::string out;
  for (auto c : input) {
    out.push_back(::toupper(c));
  }
  return out;
}

std::string ToLower(const std::string& input) {
  std::string out;
  for (auto c : input) {
    out.push_back(::tolower(c));
  }
  return out;
}
}  // namespace

// Add products and currencies
void SymbologyCache::AddProduct(std::unique_ptr<IProduct>&& product) {
  AddProduct(std::move(product), GetCurrentTimestamp());
}

void SymbologyCache::AddProduct(std::unique_ptr<IProduct>&& product, int64_t timestamp) {
  if (product == nullptr || Contains(*product)) {
    return;
  }
  product_set_.emplace(product.get());

  // using coin2::exchange::base::symbology::GetProductHolder;
  auto product_holder = GetProductHolder(*product, timestamp);

  // norm, native => IProductInfo*
  auto* pi = &(product_holder->product_info());
  product_info_absolute_map_.emplace(product->absolute_norm(), pi);
  product_info_native_map_.emplace(product->order_native_symbol(), pi);

  // norm, native => IProduct*
  product_absolute_map_.emplace(product->absolute_norm(), product.get());
  product_native_map_.emplace(product->order_native_symbol(), product.get());
  product_native_.emplace(product->order_native_symbol());

  if (is_futures() && IsDeprecatedExchange(*product, mea_)) {
    // apply new way to handle currency
    if (pi->is_inverse()) {
      currency_set_.emplace(&product->base());
      // norm, native string set
      currency_norm_.emplace(product->base().currency());
      currency_native_.emplace(product->base().native_currency());
      currency_native_upper_.emplace(ToUpper(product->base().native_currency()));
      currency_native_lower_.emplace(ToLower(product->base().native_currency()));
      // norm, native => ICurrency*
      currency_norm_map_.emplace(product->base().currency(), &product->base());
      currency_native_map_.emplace(product->base().native_currency(), &product->base());
    } else {
      currency_set_.emplace(&product->quote());
      // norm, native string set
      currency_norm_.emplace(product->quote().currency());
      currency_native_.emplace(product->quote().native_currency());
      currency_native_upper_.emplace(ToUpper(product->quote().native_currency()));
      currency_native_lower_.emplace(ToLower(product->quote().native_currency()));
      // norm, native => ICurrency*
      currency_norm_map_.emplace(product->quote().currency(), &product->quote());
      currency_native_map_.emplace(product->quote().native_currency(), &product->quote());
    }
  } else {
    // keep old way to handle currency, should be replaced with new way later
    currency_set_.emplace(&product->base());
    currency_set_.emplace(&product->quote());

    // norm, native string set
    currency_norm_.emplace(product->base().currency());
    currency_norm_.emplace(product->quote().currency());
    currency_native_.emplace(product->base().native_currency());
    currency_native_.emplace(product->quote().native_currency());

    currency_native_upper_.emplace(ToUpper(product->base().native_currency()));
    currency_native_upper_.emplace(ToUpper(product->quote().native_currency()));
    currency_native_lower_.emplace(ToLower(product->base().native_currency()));
    currency_native_lower_.emplace(ToLower(product->quote().native_currency()));

    // norm, native => ICurrency*
    currency_norm_map_.emplace(product->base().currency(), &product->base());
    currency_native_map_.emplace(product->base().native_currency(), &product->base());
    currency_norm_map_.emplace(product->quote().currency(), &product->quote());
    currency_native_map_.emplace(product->quote().native_currency(), &product->quote());
  }

  // Temporary workaround, will change later.
  if (!is_extra_symbol_initialized) {
    add_extra_symbol(mea().exchange);
    is_extra_symbol_initialized = true;
  }

  product_storage_.push_back(std::move(product));
}

void SymbologyCache::AddCurrency(std::unique_ptr<ICurrency>&& currency) {
  if (currency == nullptr || Contains(*currency)) {
    return;
  }
  currency_set_.emplace(currency.get());

  // norm, native => ICurrency*
  currency_norm_map_.emplace(currency->currency(), currency.get());
  currency_native_map_.emplace(currency->native_currency(), currency.get());

  // norm native string set
  currency_norm_.emplace(currency->currency());
  currency_native_.emplace(currency->native_currency());
  currency_native_upper_.emplace(ToUpper(currency->native_currency()));
  currency_native_lower_.emplace(ToLower(currency->native_currency()));

  currency_storage_.push_back(std::move(currency));
}

void SymbologyCache::SetGetProductHolder(GetProductHolderFunc func) { get_product_holder_ = func; }

bool SymbologyCache::Contains(const IProduct& product) const {
  // TODO(xguo): check mea
  for (auto p : GetProducts()) {
    if (product.absolute_norm() == p->absolute_norm()) {
      return true;
    }
  }
  return false;
}

bool SymbologyCache::Contains(const ICurrency& currency) const {
  // TODO(xguo): check mea
  for (auto c : GetCurrencies()) {
    if (currency.currency() == c->currency()) {
      return true;
    }
  }
  return false;
}

void SymbologyCache::add_extra_symbol(const ExchangeType& exchange_type) {
  if (exchange_type == ExchangeType::Ftx && is_futures()) {
    using coin2::exchange::ftx::symbology::FtxCurrency;
    auto timestamp = GetCurrentTimestamp();
    const static std::vector<std::string> currency_list = {
        "BTC",
        "ETH",
        "USDT",
        "FTT",
        "RAY",
        "SRM",
        "ETHW",
        "XRP",
        "DOGE",
        "CUSDT",
        "WUSDT",
        "GBTC",
        "WBTC",
        "XRP",
        "YFI",
        "UNI",
        "TRX"};
    for (const auto& currency_str : currency_list) {
      if (auto currency = FtxCurrency::FromNormStrNoThrow(currency_str, timestamp)) {
        AddCurrency(std::move(currency));
      }
    }
  }
  if (exchange_type == ExchangeType::Binance) {
    using coin2::exchange::binance::symbology::BinanceCurrency;
    auto timestamp = GetCurrentTimestamp();
    if (auto currency = BinanceCurrency::FromNormStrNoThrow("BNB", timestamp)) {
      AddCurrency(std::move(currency));
    }
    if (auto currency = BinanceCurrency::FromNormStrNoThrow("BUSD", timestamp)) {
      AddCurrency(std::move(currency));
    }
    if (auto currency = BinanceCurrency::FromNormStrNoThrow("USDC", timestamp)) {
      AddCurrency(std::move(currency));
    }
    if (auto currency = BinanceCurrency::FromNormStrNoThrow("USDT", timestamp)) {
      AddCurrency(std::move(currency));
    }
  }
  if (exchange_type == ExchangeType::Bitmex) {
    using coin2::exchange::bitmex::symbology::BitmexCurrency;
    auto timestamp = GetCurrentTimestamp();
    if (auto currency = BitmexCurrency::FromNormStrNoThrow("BTC", timestamp)) {
      AddCurrency(std::move(currency));
    }
  }
  if (exchange_type == ExchangeType::Prex) {
    using coin2::exchange::prex::symbology::PrexCurrency;
    auto timestamp = GetCurrentTimestamp();
    if (auto currency = PrexCurrency::FromNormStrNoThrow("USD", timestamp)) {
      AddCurrency(std::move(currency));
    }
  }
}

}  // namespace coin2::exchange::base::order
